import asyncio
import json
import queue
import threading
import tkinter as tk
from tkinter import scrolledtext, ttk, messagebox
from websockets.connection import State
import websockets


class WebSocketClientGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Socket客户端")
        self.root.geometry("800x600")
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

        # 连接状态
        self.connected = False
        self.websocket = None
        self.stop_event = threading.Event()
        self.message_queue = queue.Queue()

        # 创建界面
        self.create_widgets()

        # 启动消息处理线程
        self.msg_thread = threading.Thread(target=self.process_messages, daemon=True)
        self.msg_thread.start()

    def create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 配置区域
        config_frame = ttk.LabelFrame(main_frame, text="连接配置", padding="10")
        config_frame.pack(fill=tk.X, pady=(0, 10))

        # WebSocket地址
        ttk.Label(config_frame, text="服务器地址:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.ws_url_var = tk.StringVar(value="ws://127.0.0.1:2121/ws")#可修改
        ttk.Entry(config_frame, textvariable=self.ws_url_var, width=50).grid(row=0, column=1, sticky=tk.W, pady=5)

        # 用户名
        ttk.Label(config_frame, text="用户名:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.username_var = tk.StringVar(value="user01")#可修改
        ttk.Entry(config_frame, textvariable=self.username_var).grid(row=1, column=1, sticky=tk.W, pady=5)

        # 密码
        ttk.Label(config_frame, text="密码:").grid(row=2, column=0, sticky=tk.W, pady=5)
        self.password_var = tk.StringVar(value="user_key01")#可修改
        ttk.Entry(config_frame, textvariable=self.password_var, show="*").grid(row=2, column=1, sticky=tk.W, pady=5)

        # Server Password
        ttk.Label(config_frame, text="Server密码:").grid(row=3, column=0, sticky=tk.W, pady=5)
        self.server_password_var = tk.StringVar(value="serverPassword")#可修改
        ttk.Entry(config_frame, textvariable=self.server_password_var).grid(row=3, column=1, sticky=tk.W, pady=5)

        # 时间设置区域
        time_frame = ttk.LabelFrame(main_frame, text="时间设置", padding="10")
        time_frame.pack(fill=tk.X, pady=(0, 10))

        # 重连间隔
        ttk.Label(time_frame, text="重连间隔(秒):").grid(row=0, column=0, sticky=tk.W, pady=5, padx=10)
        self.reconnect_interval_var = tk.StringVar(value="5")
        ttk.Entry(time_frame, textvariable=self.reconnect_interval_var, width=10).grid(row=0, column=1, sticky=tk.W,
                                                                                       pady=5)

        # 心跳间隔
        ttk.Label(time_frame, text="心跳间隔(秒):").grid(row=0, column=2, sticky=tk.W, pady=5, padx=10)
        self.ping_interval_var = tk.StringVar(value="20")
        ttk.Entry(time_frame, textvariable=self.ping_interval_var, width=10).grid(row=0, column=3, sticky=tk.W, pady=5)

        # 心跳超时
        ttk.Label(time_frame, text="心跳超时(秒):").grid(row=0, column=4, sticky=tk.W, pady=5, padx=10)
        self.ping_timeout_var = tk.StringVar(value="10")
        ttk.Entry(time_frame, textvariable=self.ping_timeout_var, width=10).grid(row=0, column=5, sticky=tk.W, pady=5)

        # 按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=(0, 10))

        self.connect_btn = ttk.Button(button_frame, text="连接", command=self.toggle_connection)
        self.connect_btn.pack(side=tk.LEFT, padx=5)

        self.clear_btn = ttk.Button(button_frame, text="清空消息", command=self.clear_messages)
        self.clear_btn.pack(side=tk.LEFT, padx=5)

        self.status_var = tk.StringVar(value="状态: 未连接")
        ttk.Label(button_frame, textvariable=self.status_var).pack(side=tk.RIGHT, padx=5)

        # 消息显示区域
        msg_frame = ttk.LabelFrame(main_frame, text="消息记录", padding="10")
        msg_frame.pack(fill=tk.BOTH, expand=True)

        self.message_text = scrolledtext.ScrolledText(msg_frame, wrap=tk.WORD, state=tk.DISABLED)
        self.message_text.pack(fill=tk.BOTH, expand=True)

        # 发送消息区域
        send_frame = ttk.LabelFrame(main_frame, text="发送消息", padding="10")
        send_frame.pack(fill=tk.X)

        self.send_entry = scrolledtext.ScrolledText(send_frame, wrap=tk.WORD, height=3)
        self.send_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))

        self.send_btn = ttk.Button(send_frame, text="发送", command=self.send_message)
        self.send_btn.pack(side=tk.RIGHT)

    def toggle_connection(self):
        if not self.connected:
            # 验证输入
            try:
                int(self.reconnect_interval_var.get())
                int(self.ping_interval_var.get())
                int(self.ping_timeout_var.get())
            except ValueError:
                messagebox.showerror("输入错误", "时间参数必须是整数")
                return

            self.connected = True
            self.connect_btn.config(text="断开")
            self.status_var.set("状态: 连接中...")
            self.stop_event.clear()

            # 启动WebSocket客户端线程
            self.ws_thread = threading.Thread(target=self.start_websocket, daemon=True)
            self.ws_thread.start()
        else:
            self.connected = False
            self.connect_btn.config(text="连接")
            self.status_var.set("状态: 断开连接中...")
            self.stop_event.set()

    def start_websocket(self):
        asyncio.run(self.async_websocket_client())

    async def async_websocket_client(self):
        try:
            await self.main_loop()
        except Exception as e:
            self.add_message(f"错误: {str(e)}", "red")
            self.connected = False
            self.root.after(0, lambda: self.connect_btn.config(text="连接"))
            self.root.after(0, lambda: self.status_var.set("状态: 未连接"))

    def add_message(self, message, color="black"):
        self.message_queue.put((message, color))

    def process_messages(self):
        while not self.stop_event.is_set():
            try:
                message, color = self.message_queue.get(timeout=1)
                self.root.after(0, lambda m=message, c=color: self._display_message(m, c))
                self.message_queue.task_done()
            except queue.Empty:
                continue

    def _display_message(self, message, color):
        self.message_text.config(state=tk.NORMAL)
        self.message_text.insert(tk.END, message + "\n")
        # 设置最后一行的颜色
        last_line = self.message_text.index(tk.END + "-2l linestart")
        self.message_text.tag_add(color, last_line, tk.END)
        self.message_text.tag_config("blue", foreground="blue")
        self.message_text.tag_config("red", foreground="red")
        self.message_text.tag_config("green", foreground="green")
        self.message_text.config(state=tk.DISABLED)
        self.message_text.see(tk.END)

    def clear_messages(self):
        self.message_text.config(state=tk.NORMAL)
        self.message_text.delete(1.0, tk.END)
        self.message_text.config(state=tk.DISABLED)

    def send_message(self):
        if not self.connected or not self.websocket:
            messagebox.showwarning("未连接", "请先连接到服务器")
            return

        message = self.send_entry.get(1.0, tk.END).strip()
        if not message:
            return

        self.send_entry.delete(1.0, tk.END)
        self.add_message(f"发送: {message}", "blue")

        # 在异步线程中发送消息
        async def async_send():
            try:
                await self.websocket.send(message)
            except Exception as e:
                self.add_message(f"发送消息失败: {str(e)}", "red")

        asyncio.run_coroutine_threadsafe(async_send(), self.loop)

    async def login(self, websocket):
        """向服务器发送登录信息"""
        login_data = {
            'userCode': self.username_var.get(),
            'userKey': self.password_var.get(),
            'serverPassword': self.server_password_var.get()
        }
        login_str = json.dumps(login_data)
        await websocket.send(login_str)
        self.add_message(f"发送登录信息: {login_str}", "blue")

        response = await websocket.recv()
        self.add_message(f"收到登录响应: {response}", "green")
        return json.loads(response)

    async def timer_ping(self, websocket, interval):
        """增强版心跳：检测Pong响应超时"""
        try:
            ping_timeout = int(self.ping_timeout_var.get())
            while not self.stop_event.is_set():
                await asyncio.sleep(interval)
                if websocket.state == State.OPEN:
                    self.add_message("发送心跳 ping...", "blue")
                    try:
                        # 发送Ping并等待Pong响应，超时则触发重连
                        await asyncio.wait_for(websocket.ping(), timeout=ping_timeout)
                        self.add_message("收到 pong 响应", "green")
                    except asyncio.TimeoutError:
                        self.add_message("Ping超时，触发重连", "red")
                        # 主动抛出断开异常，触发重连
                        raise websockets.ConnectionClosedError(rcvd=None, sent=None)
                else:
                    self.add_message("WebSocket 连接已关闭，停止发送 ping", "red")
                    break
        except Exception as e:
            self.add_message(f"发送 ping 出错: {e}", "red")
            raise

    async def receive_messages(self, websocket):
        """接收来自服务器的消息"""
        message_count = 0
        try:
            async for message in websocket:
                if self.stop_event.is_set():
                    break

                message_count += 1
                self.add_message(f"收到消息 {message_count}: {message}", "green")

                # 发送确认消息
                try:
                    message_arr = json.loads(message)
                    confirm_data = {
                        'type': "ack",
                        'message': message_arr['id'],
                        'userCode': message_arr['userCode']
                    }
                    confirm_str = json.dumps(confirm_data)
                    await websocket.send(confirm_str)
                    self.add_message(f"发送确认: {confirm_str}", "blue")
                except Exception as e:
                    self.add_message(f"发送确认消息失败: {e}", "red")
        except websockets.ConnectionClosed as e:
            self.add_message(f"连接已关闭: {e}", "red")
            raise
        except Exception as e:
            self.add_message(f"接收消息出错: {e}", "red")
            raise

    async def reconnect(self):
        """尝试重新连接 WebSocket 服务器"""
        reconnect_interval = int(self.reconnect_interval_var.get())
        while not self.stop_event.is_set():
            try:
                self.add_message("尝试重新连接...", "blue")
                await self.main_loop()
                break  # 如果成功连接，则退出循环
            except Exception as e:
                self.add_message(f"重连失败: {e}", "red")
                await asyncio.sleep(reconnect_interval)  # 等待一段时间再尝试重连

    async def main_loop(self):
        """主函数：建立连接、登录并开始接收消息"""
        try:
            async with websockets.connect(self.ws_url_var.get()) as websocket:
                self.websocket = websocket
                self.loop = asyncio.get_running_loop()
                self.add_message("已连接到服务器", "green")
                self.root.after(0, lambda: self.status_var.set("状态: 已连接"))

                # 登录
                response = await self.login(websocket)
                if response.get('message') == '登录成功':
                    self.add_message("登录成功", "green")

                    # 创建两个任务：接收消息 和 发送 ping
                    ping_interval = int(self.ping_interval_var.get())
                    receiver_task = asyncio.create_task(self.receive_messages(websocket))
                    ping_task = asyncio.create_task(self.timer_ping(websocket, ping_interval))

                    # 等待任意一个任务完成（比如连接断开）
                    done, pending = await asyncio.wait(
                        [receiver_task, ping_task],
                        return_when=asyncio.FIRST_COMPLETED
                    )

                    # 取消未完成的任务
                    for task in pending:
                        task.cancel()

                    if self.stop_event.is_set():
                        self.add_message("主动断开连接", "blue")
                        return

                    # 如果不是正常关闭，触发重连
                    raise websockets.ConnectionClosedError(rcvd=None, sent=None)
                else:
                    self.add_message("登录失败", "red")
                    self.root.after(0, lambda: self.status_var.set("状态: 登录失败"))
                    self.connected = False
                    self.root.after(0, lambda: self.connect_btn.config(text="连接"))
        except websockets.ConnectionClosedOK:
            self.add_message("连接正常关闭", "blue")
        except Exception as e:
            self.add_message(f"主循环异常: {e}", "red")
            if not self.stop_event.is_set():
                self.root.after(0, lambda: self.status_var.set("状态: 连接断开，尝试重连..."))
                await self.reconnect()
            else:
                self.root.after(0, lambda: self.status_var.set("状态: 未连接"))

    def on_close(self):
        self.stop_event.set()
        self.connected = False
        self.root.destroy()


if __name__ == "__main__":
    root = tk.Tk()
    app = WebSocketClientGUI(root)
    root.mainloop()